/*
 * Copyright (C) 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This code is not supported by Google
 *
 */

package com.google.enterprise.gsa.commerce;
/*
 * Submit Google Search Appliance search requests and parses the response xml search results.
 * 
 * To use, see the 'main' method of GSAClient.java:
 *    * define the collection, frontend, GSA hostname
 *    * submit a search query
 *    * define filters (restricts)
 *    * define the startPage
 *    * parse the response stream for the results, item attributes for each result,
 *      spelling suggestions and facet counts
 *    
 * http://www.google.com/support/enterprise/static/gsa/docs/admin/70/gsa_doc_set/xml_reference/
 * 
 * 
 */
import java.util.*;
import java.io.*;
import java.net.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.*;
import javax.xml.namespace.*;


import java.net.HttpURLConnection;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLSession;


public class GSAClient {

    private static Logger logger = Logger.getLogger(GSAClient.class.getPackage().getName());
    
    private String host, collection, front_end;
	private ArrayList<String> restricts;
	private int start = 0;
	private int maxResults = 10;
	private String query_string = "";
	private String query = "";	
	
	public static void main(String[] args) {
		
		String host = "http://<YOUR_GSA>";
		String query = "san francisco";
		String collection = "your_collection";
		String front_end = "your_frontend";

		GSAClient g = new GSAClient(host, collection, front_end);
		g.setQuery(query);
		g.setMaxResults(2);
		g.addRestrict("price:1..10");
		g.addRestrict("brand=acmeg");
		g.setStartPage(0);
		try{
			Result r = g.execute();			
			System.out.println(r.getElapsedTime());
		    System.out.println(r.getTotalResults());
		    System.out.println(r.getQuery());
		    		        
		    //QueryParameters:
		    //for (Map.Entry<String, String> entry : r.getQueryParams().entrySet())
		    //	System.out.println("Query Parameter: " + entry.getKey() + " " + entry.getValue());
  	        
		    for (String res : r.getRestricts())
		    	System.out.println("Restricts: " + res);
		    
		    if (r.has_spelling_suggestions())
		    	for (String s : r.getSpellingSuggestions())
		    		System.out.println("Spelling suggestion: " + s);
		    
		    if (r.has_oneboxresults())
		    	for (OneBoxResult obr : r.getOneBoxResults())
		    	{		    		
		    		if (obr.isPromotion())
		    			System.out.println("Promotion: ");
		    		
		    		if (obr.isRedirect())
		    			System.out.println("Redirect: ");
		    		
		    		for (Map.Entry<String, String> entry : obr.getKVParis().entrySet())
		    			System.out.println("     Parameter: " + entry.getKey() + "-->" + entry.getValue());
		    	}
		    
		    if (r.has_keymatches())
		    	for (HashMap<String,String> h : r.getKeyMatches())
		    	{
		    		System.out.println("KeyMatch Title: " + (String)h.get(Result.KEYMATCH_TITLE));
		    		System.out.println("KeyMatch URL: " + (String)h.get(Result.KEYMATCH_URL));		    		
		    	}	    
		    
		    for (Product p : r.getProducts()) {
		    	System.out.println(p.getTitle());
		    	System.out.println(p.getLink());
		    	for (String s: p.getAttribute("image_link"))
		    		System.out.println(s);
		    }		     
		    	    
		    
		    for (Facet f : r.getFacets()) {
		    	System.out.println(f.getName());
		    	System.out.println("Display Name: " + f.getDisplayName());
		    	for (FacetValue fv : f.getFacetValues()) {
		    		if (f.isRangeValue())
		    			System.out.println(fv.getMin() + ".." + fv.getMax() + " --> " + fv.getCount());
		    		else
		    			System.out.println(fv.getName() + " --> " + fv.getCount());
		    	}
		    }
		    
		    
		}
		catch (Exception ex) {
			logger.log(Level.SEVERE, "Error " + ex);
		}
	}

	public GSAClient(String host, String collection, String front_end) {
		this.host = host;
		this.collection = collection;
		this.front_end = front_end;
		this.restricts = new ArrayList<String>();
	}
	
	public void setQuery(String query) {
		this.query = query;
	}
	
	public Result execute() throws Exception{
		String q = URLEncoder.encode(query,"UTF-8");
		String dnav = "";
  	    for (String s : this.restricts)
		  dnav = dnav+ URLEncoder.encode(" inmeta:" + s, "UTF-8");
		query_string = host + "/search?q=" + q.trim() + "&client=" + front_end + "&output=xml_no_dtd&getfields=*&filter=0&site=" + collection + "&start=" + this.start + "&num=" + maxResults + "&as_q=" + dnav;		
		logger.log(Level.INFO, " Run query " + this.query_string);
		
	         TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
	             public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
	             public void checkClientTrusted(X509Certificate[] certs, String authType) {  }
	             public void checkServerTrusted(X509Certificate[] certs, String authType) {  }
		       }
		      };
		  
            SSLContext sc = SSLContext.getInstance("SSL");
	        sc.init(null, trustAllCerts, new java.security.SecureRandom());
	        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
	        HostnameVerifier allHostsValid = new HostnameVerifier() {
	            public boolean verify(String hostname, SSLSession session) { return true; }
	         };
	        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
	        
	        URL url = new URL(this.query_string);
	        HttpURLConnection con = (HttpURLConnection) url.openConnection();
	        
	        if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
	        	return this.parseResponse(con.getInputStream());
	        }
	        else {
	    		logger.log(Level.SEVERE, "Error Executing query. HTTP Error code: " + con.getResponseCode() + " " + query_string);
	    		throw new Exception("Error Executing query. HTTP Error code " + con.getResponseCode() + " " + query_string);
	        }

	}
	
	public Result parseResponse(InputStream is) throws Exception{
		DocumentBuilderFactory docBF = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = (DocumentBuilder) docBF.newDocumentBuilder();
		Document doc = null;
		try {	
			doc = docBuilder.parse(is);
			doc.getDocumentElement().normalize();
		}
		catch (Exception ex) {
    		logger.log(Level.SEVERE, "Unable to parse response " );
    		throw new Exception("Unable to parse response");			
		}
		
		//namespaces not necessary for the GSA's xml response
		NamespaceContext ctx = new NamespaceContext()
			{
				public String getNamespaceURI(String prefix) {
					String uri;
					if (prefix.equals("atom"))
						uri = "http://www.w3.org/2005/Atom";
					else
						uri = null;
					return uri;
				}
				public Iterator getPrefixes(String val) {
					return null;
				}         
				public String getPrefix(String uri) {
					return null;
				}
			};

	        XPathFactory xpathFactory = XPathFactory.newInstance();
		    XPath xpath = xpathFactory.newXPath();
		    xpath.setNamespaceContext(ctx);
		    
		    Result r = new Result();
		    
		    Node n_elapsed_time = (Node) xpath.evaluate("/GSP/TM", doc, XPathConstants.NODE);
		    Node n_q = (Node) xpath.evaluate("/GSP/Q", doc, XPathConstants.NODE);
		    Node n_totalResults= (Node) xpath.evaluate("/GSP/RES/M", doc, XPathConstants.NODE);
		    NodeList nl_params = (NodeList)xpath.evaluate("/GSP/PARAM", doc, XPathConstants.NODESET);
		    NodeList nl_oneboxresults = (NodeList)xpath.evaluate("/GSP/ENTOBRESULTS/OBRES/MODULE_RESULT", doc, XPathConstants.NODESET);
		    NodeList nl_results = (NodeList)xpath.evaluate("/GSP/RES/R", doc, XPathConstants.NODESET);
		    NodeList nl_spelling_suggestion = (NodeList)xpath.evaluate("/GSP/Spelling/Suggestion", doc, XPathConstants.NODESET);
		    NodeList nl_keymatches = (NodeList)xpath.evaluate("/GSP/GM", doc, XPathConstants.NODESET);
		    NodeList nl_facets = (NodeList)xpath.evaluate("/GSP/RES/PARM/PMT", doc, XPathConstants.NODESET);
		   
		    r.setElapsedTime(Float.parseFloat(n_elapsed_time.getTextContent()));		    
		    r.setQuery(n_q.getTextContent());
		    
		    if (n_totalResults != null)
		    	r.setTotalResults(Integer.parseInt(n_totalResults.getTextContent()));
		    else
		    	r.setTotalResults(0);
		   
		    if (nl_spelling_suggestion!=null) 
		    	for (int i=0; i<nl_spelling_suggestion.getLength(); i++)
		    		r.addSpellingSuggestion(nl_spelling_suggestion.item(i).getAttributes().getNamedItem("q").getTextContent());
		    
		    if (nl_oneboxresults!=null)
		    	for (int i=0; i<nl_oneboxresults.getLength(); i++)
		    	{
		    		OneBoxResult obr = new OneBoxResult();
		    		Node n_title = (Node)xpath.evaluate("Title", nl_oneboxresults.item(i), XPathConstants.NODE);
		    		if (n_title != null)
		    			obr.setTitle(n_title.getFirstChild().getTextContent());
		    		NodeList nl_fields = (NodeList)xpath.evaluate("Field", nl_oneboxresults.item(i), XPathConstants.NODESET);
		    		for (int j=0; j< nl_fields.getLength(); j++)
		    		{
		    			String name = nl_fields.item(j).getAttributes().getNamedItem("name").getTextContent();
		    			String value = nl_fields.item(j).getFirstChild().getTextContent();	
		    			obr.addKVPair(name, value);
		    		}
		    		r.addOneBoxResult(obr);
		    	}
		    	
		    
		    if (nl_keymatches!=null)
		    	for (int i=0; i<nl_keymatches.getLength(); i++)
		    	{
		    		
		    		Node n_gl = (Node)xpath.evaluate("GL", nl_keymatches.item(i), XPathConstants.NODE);
		    		Node n_gm = (Node)xpath.evaluate("GD", nl_keymatches.item(i), XPathConstants.NODE);
		    		String title = "";
		    		String url = "";
		    		if (n_gl != null)
		    			url = n_gl.getFirstChild().getTextContent();
		    		if (n_gm != null)
		    			title = n_gm.getFirstChild().getTextContent();
		    		r.addKeyMatch(title, url);
		    	}
		    
		    for (int i=0; i<nl_params.getLength(); i++) {
		    	String key = nl_params.item(i).getAttributes().getNamedItem("name").getTextContent();
		    	String value = nl_params.item(i).getAttributes().getNamedItem("value").getTextContent();
		    	r.addQueryParam(key,value);
		    }
		    
			// hack to 'add restricts' into the result object...
			// alternatives to this is to parse out the returned as_q= parameter in the response and then add 
			for (String s : this.getRestricts())
				r.addRestrict(s);
		    
		    
		    
		   			    
		    for (int i=0; i<nl_results.getLength(); i++) {
		    	Product p = new Product();
		    	Node n_url = (Node) xpath.evaluate("U", nl_results.item(i), XPathConstants.NODE);
		    	p.setLink(n_url.getTextContent());
		    	NodeList nl_attributes = (NodeList)xpath.evaluate("MT", nl_results.item(i), XPathConstants.NODESET);
		    	for (int j=0;j<nl_attributes.getLength(); j++) {
		    		String key = nl_attributes.item(j).getAttributes().getNamedItem("N").getTextContent();
		    		String value = nl_attributes.item(j).getAttributes().getNamedItem("V").getTextContent();
		    		p.addAttribute(key,value);
		    	}
		    	r.addProduct(p);		    	
		    }

		    for (int i=0; i<nl_facets.getLength(); i++) {
		    	String name = nl_facets.item(i).getAttributes().getNamedItem("NM").getTextContent();
		    	String display_name = nl_facets.item(i).getAttributes().getNamedItem("DN").getTextContent();
		    	String type = nl_facets.item(i).getAttributes().getNamedItem("IR").getTextContent();
		    	Facet f = new Facet(name, display_name);
		    	NodeList nl_facetvalue = (NodeList)xpath.evaluate("PV", nl_facets.item(i), XPathConstants.NODESET);
		    	for (int j=0; j<nl_facetvalue.getLength(); j++) {
		    		if (type.equals(Facet.RANGE)) {
		    			String min = nl_facetvalue.item(j).getAttributes().getNamedItem("L").getTextContent();
		    			String max = nl_facetvalue.item(j).getAttributes().getNamedItem("H").getTextContent();
		    			String count = nl_facetvalue.item(j).getAttributes().getNamedItem("C").getTextContent();
		    			if (max.equals(""))
		    				f.addRangeValue(Float.parseFloat(min) ,Float.POSITIVE_INFINITY, Integer.parseInt(count));
		    			else
		    				f.addRangeValue(Float.parseFloat(min) ,Float.parseFloat(max), Integer.parseInt(count));		    				
		    		}
		    		else {
		    			String V = nl_facetvalue.item(j).getAttributes().getNamedItem("V").getTextContent();
		    			String count = nl_facetvalue.item(j).getAttributes().getNamedItem("C").getTextContent();
		    			f.addTextValue(V, Integer.parseInt(count));
		    		}
		    	}
      		  r.addFacet(f);
		    }
	   
		return r;
	}
	
	
	public void addRestrict(String r) {
		this.restricts.add(r);
	}
	
	public ArrayList<String> getRestricts() {
		return this.restricts;
	}
	
	public void setStartPage(int start) {
		this.start = start;
	}
	
	public void setMaxResults(int maxResults) {
		this.maxResults = maxResults;
	}
}
